package cn.demoncat.util.mysql.util;

import cn.demoncat.util.exception.IoRuntimeException;
import cn.demoncat.util.io.CloseUtil;
import cn.demoncat.util.io.FileUtil;
import cn.demoncat.util.io.FreeMarkerUtil;
import cn.demoncat.util.io.ZipUtil;
import cn.demoncat.util.lang.JDBCUtil;
import cn.demoncat.util.lang.LocalDateUtil;
import cn.demoncat.util.lang.constant.StringConstant;
import cn.demoncat.util.lang.entity.Result;
import cn.demoncat.util.mysql.entity.*;
import cn.demoncat.util.web.ServletUtil;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;

/**
 * JPA代码生成工具
 *
 * @author 延晓磊
 * @since 2021年07月28日
 */
public class JpaCodeGenUtil {


    // 模板路径
    private static final String TEMPLATE_DIR = "/ftl/jpa";
    // 临时目录
    private static final String TMP_DER = "/tmp/";


    private static final Logger LOG = LoggerFactory.getLogger(JpaCodeGenUtil.class);

    /**
     * 本地生成代码
     *
     * @param out			输出目录，后缀/
     * @param db			数据库
     * @param tableName		表名：* 表示全部，xx* 前缀匹配，xx 单表，xx,yy 多表
     * @param author		作者
     * @param classNamePre	类名前缀，区分多数据源的同名表："" | Xxx
     * @param packageName	包名前缀：cn.xxx.xxx
     *
     * @author 延晓磊
     *
     * @since 2019年7月3日
     */
    public static void localGen(String out, DbGenParam db, String tableName, String author, String classNamePre, String packageName) {
        if (!out.endsWith(StringConstant.SEPARATOR)) {
            out = out + StringConstant.SEPARATOR;
        }
        // 查询数据库表
        List<MysqlTable> tableInfos = DbGenUtil.listTableInfos(db, DbGenUtil.listTables(db, tableName));
        // 生成代码
        OutputStream output = null;
        try {
            output = new FileOutputStream(out + db.getName() + ".zip");
            gen(db, tableInfos, author, classNamePre, packageName, output);
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            CloseUtil.close(output);
        }
    }

    /**
     * WEB服务生成代码
     *
     * @param response
     * @param db			数据库
     * @param tableName		表名：* 表示全部，xx* 前缀匹配，xx 单表，xx,yy 多表
     * @param author		作者
     * @param classNamePre	类名前缀，区分多数据源的同名表："" | Xxx
     * @param packageName	包名前缀：cn.xxx.xxx
     *
     * @author 延晓磊
     *
     * @since 2019年7月3日
     */
    public static Result webGen(HttpServletResponse response, DbGenParam db, String tableName, String author, String classNamePre, String packageName) {
        // 查询数据库表
        List<MysqlTable> tableInfos = DbGenUtil.listTableInfos(db, DbGenUtil.listTables(db, tableName));
        // 下载代码压缩包
        gen(db, tableInfos, author, classNamePre, packageName, ServletUtil.getStreamOut(response, db.getName() + ".zip"));
        // 下载成功返回null，避免SpringMvc二次输出响应
        return null;
    }

    /**
     * 生成代码
     *
     * @param db			数据库信息
     * @param tableInfos	表信息
     * @param author		作者
     * @param classNamePre	类名前缀，区分多数据源的同名表："" | Xxx
     * @param packageName	包名前缀：cn.xxx.xxx
     * @param out			输出流
     *
     * @author 延晓磊
     *
     * @since 2019年7月3日
     */
    private static void gen(DbGenParam db, List<MysqlTable> tableInfos,String author, String classNamePre,String packageName,OutputStream out) {
        // 创建结果目录
        String resultDir = TMP_DER + System.currentTimeMillis();
        File resultFile = new File(resultDir);
        // 生成代码
        try {
            generate(db, tableInfos, author, classNamePre, packageName, resultFile, resultDir, out);
        }finally {
            // 删除结果文件
            FileUtil.delFile(resultFile);
        }
    }

    /**
     * 生成代码
     *
     * @param db
     * @param tableInfos
     * @param author
     * @param packageName	根包名
     * @param resultFile
     * @param resultDir		结果目录名
     * @param out			输出流
     *
     * @author 延晓磊
     *
     * @since 2019年7月3日
     */
    private static void generate(DbGenParam db, List<MysqlTable> tableInfos,String author,String classNamePre, String packageName,File resultFile,String resultDir,OutputStream out) {
        // 结果目录
        String entityDir = resultDir + "/pojo/";
        String daoDir = resultDir + "/dao/";
        new File(entityDir).mkdirs();
        new File(daoDir).mkdirs();
        // 包名
        String entityPackage = packageName + ".pojo";
        String daoPackage = packageName + ".dao";
        // 遍历表信息
        DbGenTable table;
        DbGenField field;
        List<DbGenField> fields;
        DbGenEntity entity;
        DbGenAttr attr;
        List<DbGenAttr> attrs;
        for (MysqlTable tableInfo : tableInfos) {
            LOG.info("开始生成 "+ tableInfo.getName() +" 表的代码 ......");
            // 创建表
            table = new DbGenTable();
            table.setName(tableInfo.getName());
            table.setJavaName(JDBCUtil.toJavaName(tableInfo.getName()));
            table.setComment(tableInfo.getComment().endsWith("表")?tableInfo.getComment().substring(0, tableInfo.getComment().length()-1):tableInfo.getComment());
            // 创建实体类
            entity = new DbGenEntity();
            entity.setPackageName(entityPackage);
            entity.setComment(table.getComment());
            entity.setSimpleName(JDBCUtil.toJavaName(StringUtils.isBlank(classNamePre)? table.getName() : classNamePre.toLowerCase()+"_"+table.getName()));
            entity.setName(entity.getSimpleName().substring(0, 1).toUpperCase() + entity.getSimpleName().substring(1));
            entity.setFullName(entity.getPackageName() + "." + entity.getName());
            entity.setUrl(StringConstant.SEPARATOR + StringUtils.replace(table.getName().toLowerCase(), StringConstant.UNDER_LINE, StringConstant.SEPARATOR));
            entity.setAuthor(author);
            entity.setTime(LocalDateUtil.toDateStr());
            // 创建表字段和属性
            fields = new ArrayList<>();
            attrs = new ArrayList<>();
            for (MysqlColumn column : tableInfo.getColumns()) {
                // 创建表字段
                field = new DbGenField();
                field.setName(column.getName());
                field.setComment(column.getComment());
                field.setJavaName(JDBCUtil.toJavaName(column.getName()));
                field.setType(column.getType());
                field.setJdbcType(JDBCUtil.getJdbcType(column.getType()));
                fields.add(field);
                attr = new DbGenAttr();
                attr.setField(field);
                attr.setName(JDBCUtil.toJavaName(field.getName()));
                attr.setComment(field.getComment());
                attr.setType(JDBCUtil.getJavaType(field.getType()));
                attrs.add(attr);
            }
            table.setFields(fields);
            entity.setAttrs(attrs);
            // 数据模型
            HashMap<String, Object> data = new HashMap<>();
            // 存入序列化值、名称
            data.put("table", table);
            data.put("entity", entity);
            data.put("dao", new DbGenMapper(daoPackage, null));
            // 使用模板生成文件
            generate(table.getName(), data, "entity.ftl", entityDir + entity.getName() + ".java");
            generate(table.getName(), data, "dao.ftl", daoDir + entity.getName() + "Repository.java");
        }

        // 去除"\65279"编码错误
        try {
            cleanBom(resultFile);
        } catch (IOException e) {
            throw new IoRuntimeException("整理代码文件失败", e);
        }

        // 压缩输出
        ZipUtil.zip(resultFile, out);
    }

    /**
     * 生成代码文件
     *
     * @param tableName		表名
     * @param data			数据
     * @param template		模板名
     * @param resultFile	结果文件路径
     *
     * @author 延晓磊
     *
     * @since 2019年7月3日
     */
    private static void generate(String tableName, HashMap<String, Object> data, String template, String resultFile) {
        try {
            FreeMarkerUtil.out(TEMPLATE_DIR, template, data, new FileOutputStream(resultFile));
        } catch (FileNotFoundException e) {
            throw new IoRuntimeException("生成 "+tableName+" 表的代码失败", e);
        }
    }

    /**
     * 去除"\65279"编码错误
     *
     * 问题：Eclipse生成文件时，会标记"\65279"头信息，导致代码运行时报错
     * 解决：将文件转换为ISO-8859-1，然后删除首个乱码字符
     *
     * @throws IOException
     */
    private static void cleanBom(File resultFile) throws IOException {
        // 读取的所有生成的文件
        Collection<File> files = FileUtils.listFiles(resultFile, null, true);
        for (File file : files) {
            // 读取文件
            String s = FileUtils.readFileToString(file, "ISO-8859-1");
            // 去除"\65279"
            s = StringUtils.strip(s, "\65279");
            s = StringUtils.strip(s, "ï»¿");
            // 写入文件
            FileUtils.write(file, s, "ISO-8859-1");
        }
    }

}
